package pl.smath.operation.basic;

import java.util.List;

import pl.smath.expression.ExponentiationTerm;
import pl.smath.expression.FractionTerm;
import pl.smath.expression.NumberTerm;
import pl.smath.expression.ProductTerm;
import pl.smath.expression.Term;


/**
 * Zawiera podstawowe operacje no modelu.
 * 
 * @author klangner
 *
 */
public class MultiplyOp extends Operation{

	/**
	 * Ta funkcja gwarantuje ze wyrazenie zostanie wymnozone.
	 * Jezeli nie bedzie to mozliwe to zostanie zwrocone wyrazenie typu t1*t2
	 */
	public Term forceMultiply(Term t1, Term t2){
		Term outputTerm = process(t1, t2);
		boolean negative = false;
		
		if(outputTerm == null){
		
			ProductTerm pt = new ProductTerm();
			Term t;
			
			t = t1.copy();
			if(t.isNegative()){
				t.setNegative(false);
				negative = true;
			}
			pt.addTerm(t);
			
			t = t2.copy();
			if(t.isNegative()){
				t.setNegative(false);
				negative = !negative;
			}
			pt.addTerm(t);
			pt.setNegative(negative);
			
			outputTerm = pt;
		}
		
		return outputTerm;
	}
	
	
	/**
	 * Dodaje do siebie 2 wyrazenie. Jezeli wyrazenia nie moga byc dodane to zwracana jest wartosc null.
	 *  
	 * @param t1
	 * @param t2
	 * @return Nowy obiekt bedacy sumą wyrazen lub null jezeli wyrazenie nie moga byc dodane
	 */
	public Term process(Term t1, Term t2){

		if(t1 instanceof NumberTerm){
		
			if(t2 instanceof NumberTerm){
				// Dodajemy do siebie 2 liczby
				float value = ((NumberTerm)t1).getValue() * ((NumberTerm)t2).getValue();
				
				return new NumberTerm(value);
			}
			else if(t2 instanceof FractionTerm){
				return multiplyByFraction((NumberTerm)t1, (FractionTerm) t2);
			}
		}
		else if(t1 instanceof FractionTerm){
			return multiplyByFraction(t2, (FractionTerm) t1);
		}
		else if(t1 instanceof ExponentiationTerm){
			
			return multiplyByExponent((ExponentiationTerm)t1, t2);
		}
		else if(t2 instanceof ExponentiationTerm){
			
			return multiplyByExponent((ExponentiationTerm)t2, t1);
		}
		else if(t1.isTheSame(t2)){
			return new ExponentiationTerm(t1.copy(), new NumberTerm(2));
		}

		return null;
	}
	
	
	/**
	 * Zamiana listy podwyrazen na wyrazenia. Implementacja zalezy od operacji
	 * @param terms
	 * @return
	 */
	protected Term mergeTerms(List<Term> terms){
		ProductTerm sumTerm = new ProductTerm();
		
		// skopiowanie wszystkich roznych od 1
		for(int i = 0; i < terms.size(); i++){
			
			Term t = terms.get(i);
			if(!(t instanceof NumberTerm) || ((NumberTerm)t).getValue() != 1){
				sumTerm.addTerm(t);
			}
		}
		
		if(sumTerm.getTermsList().size() == 0)
			return new NumberTerm(1);
			
		return sumTerm;
		
	}

	/**
	 * wymnozenie ulamka z liczba
	 */
	private Term multiplyByFraction(Term t1, FractionTerm t2) {

		Term up = process(t1, t2.getNumerator());
		
		if(up == null){
			ProductTerm pt = new ProductTerm();
			pt.addTerm(t1);
			pt.addTerm(t2.getNumerator());
			up = pt;
		}
		
		return new FractionTerm(up, t2.getDenominator());
	}

	/**
	 * Mnozenie przez wyrazenie wykladnicze
	 */
	private Term multiplyByExponent(ExponentiationTerm t1, Term t2) {

		if(t1.getBase().isTheSame(t2)){
			AddOp ap = new AddOp();
			Term expTerm = ap.forceAdd(t1.getExponent(), new NumberTerm(1));
			
			return new ExponentiationTerm(t1.getBase(), expTerm);
		}
		else if(t2 instanceof ExponentiationTerm && 
				t1.getBase().isTheSame(((ExponentiationTerm)t2).getBase()))
		{
			AddOp ap = new AddOp();
			Term exp2 = ((ExponentiationTerm)t2).getExponent();
			Term expTerm = ap.forceAdd(t1.getExponent(), exp2);

			return new ExponentiationTerm(t1.getBase(), expTerm);
		}

		return null;
	}


	
}
